/**
 * klasse die das 015 Robosuitcase verwaltet
 * 
 * zugriff auf kofferfunktionen laeuft einzig ueber diese klasse.
 */
package ch.unizh.ifi.ailab.suitcase;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ConnectException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;

import ch.unizh.ifi.ailab.ai.AICore;
import ch.unizh.ifi.ailab.ai.UnavoidableObjectException;
import ch.unizh.ifi.ailab.config.ProgramSettings;
import ch.unizh.ifi.ailab.dataTypes.Command;
import ch.unizh.ifi.ailab.dataTypes.SensorData;



/**
 * @author hans jaeckle jaeckle@gmail.com
 * 
 */

public class Suitcase extends Thread {
	// wenn nicht funzt dann ev. Pattern p=new
	// Pattern("pattern",Pattern.LINEBREAK_TREATMENT)
	// TODO: regex in die properties
	private static final Pattern irPattern = Pattern
			.compile("Infrarot Distanz-Sensoren.*?<b>(\\d+)</b>.*?<b>(\\d+)</b>.*?<b>(\\d+)</b>.*?<b>(\\d+)</b>");

	private static final Pattern usPattern = Pattern
			.compile("IO-Board Ultraschall-Sensoren.*?<b>(\\d+)</b>.*?<b>(\\d+)</b>.*?<b>(\\d+)</b>.*?<b>(\\d+)</b>");

	private static final Pattern ioPattern = Pattern
			.compile("IO-Board Digital-Sensoren.*?<b>(\\d+)</b>");

	private boolean running = false;

	private boolean runningRemote = true;

	private boolean connected = false;

	private Command currentCommand = new Command();

	private long lastUpdate = 0;

	private long timeout;

	// ki
	private AICore myKI = null;

	// properties
	private ProgramSettings myProp = ProgramSettings.getProgramSettings();

	// logging
	private Logger myLog = Logger.getLogger(Suitcase.class);

	/**
	 * konstruktor methode - privat da singleton. es kann nur einen geben!
	 */
	public Suitcase() {
		myLog.debug("koffer: initialisiere koffer");
		timeout = (long) myProp.getIntProperty("updateTimeSuitcaseKI");
		myLog.debug("koffer: setze timeout: " + timeout);
	}

	/**
	 * run methode des thread - mit dem loop
	 */
	public void run() {
		// die run methode des thread...
		Command command = new Command();
		currentCommand = command;
		while (running) {
			long now = System.currentTimeMillis();
			if (now - lastUpdate >= timeout) {
				if (runningRemote) {
					// veraenderungen durch increase und so
					command = currentCommand;
				}
				doSuitcaseControl(command);
				currentCommand = command;
				lastUpdate = now;
			} else {
				try {
					// TODO: sleep abhaengig von now-lastupdate machen
					sleep(myProp.getIntProperty("sleepTime"));
				} catch (InterruptedException e) {
					error(e);
				}
			}
		}
		myLog.info("koffer: thread beendet.");
		return;
	}

	/**
	 * speedIncrease methode erh�ht die geschwindigkeit um ein inkrement (siehe properties)
	 * 
	 * diese mehode ist ausgeschaltet im ki modus
	 */
	public void speedIncrease() {
		if (runningRemote) {
			currentCommand.setSpeed(currentCommand.getSpeed()
					+ myProp.getIntProperty("speedIncrement"));
		}
	}

	/**
	 * speedDecrease methode verringert die geschwindigkeit um ein inkrement (siehe properties)
	 * 
	 * diese mehode ist ausgeschaltet im ki modus
	 */
	public void speedDecrease() {
		if (runningRemote) {
			currentCommand.setSpeed(currentCommand.getSpeed()
					- myProp.getIntProperty("speedIncrement"));
			myLog.debug("decreasing speed");
		}
	}

	/**
	 * seedStop methode setzt den antrieb auf null
	 * 
	 * diese mehode ist ausgeschaltet im ki modus
	 */
	public void speedStop() {
		if (runningRemote) {
			currentCommand.setSpeed(myProp.getIntProperty("speedStop"));
		}
	}

	/**
	 * directionLeft methode steuert um ein inkrement (siehe properties) mehr nach links
	 * 
	 * diese mehode ist ausgeschaltet im ki modus
	 */
	public void directionLeft() {
		if (runningRemote) {
			currentCommand.setDirection(currentCommand.getDirection()
					+ myProp.getIntProperty("directionIncrement"));
		}
	}

	/**
	 * directionRight methode steuert um ein inkrement (siehe properties) mehr nach rechts
	 * 
	 * diese mehode ist ausgeschaltet im ki modus
	 */
	public void directionRight() {
		if (runningRemote) {
			currentCommand.setDirection(currentCommand.getDirection()
					- myProp.getIntProperty("directionIncrement"));
		}
	}

	/**
	 * directionCenter methode setzt die steuerung auf geradeaus
	 * 
	 * diese mehode ist ausgeschaltet im ki modus
	 */
	public void directionCenter() {
		if (runningRemote) {
			currentCommand.setDirection(myProp.getIntProperty("dirCenter"));
		}
	}

	/**
	 * emergencyStop setzt speed und direction auf null und schaltet ki aus.
	 */
	public void emergencyStop() {
		runningRemote = true;
//		int breakIntensity = myProp.getIntProperty("speedStop")
//				- currentCommand.getSpeed();
		int breakIntensity = -9;
		currentCommand.setSpeed(myProp.getIntProperty("speedStop")
				+ breakIntensity);
		doSuitcaseControl(currentCommand);
		try {
			sleep(myProp.getIntProperty("emergencyBreakTime"));
		} catch (InterruptedException e) {
			myLog.error("something bad happend..." + e.toString());
		}
		speedStop();
		directionCenter();
	}

	/**
	 * ueberprueft ob das uebergebene kommando innerhalb der grenzwerte liegt
	 * und korrigiert gegebenenfalls
	 * 
	 * @param command
	 */
	private void checkCommand(Command command) {
		/**
		 * pre: kommando mit irgendwelchen werten
		 * 
		 * post: werte im kommando werden auf max gesetzt wenn sie gr�sser als
		 * zulaessig, auf min wenn kleiner als zulaessig und gelassen wenn
		 * innerhalb der grenzen
		 */
		// ueberpruefung der geschwindigkeit
		if (command.getSpeed() > myProp.getIntProperty("speedMax")) {
			command.setSpeed(myProp.getIntProperty("speedMax"));
		} else if (command.getSpeed() < myProp.getIntProperty("speedMin")) {
			command.setSpeed(myProp.getIntProperty("speedMin"));
		}
		// ueberpruefung der richtung
		if (command.getDirection() > myProp.getIntProperty("dirMax")) {
			command.setDirection(myProp.getIntProperty("dirMax"));
		} else if (command.getDirection() < myProp.getIntProperty("dirMin")) {
			command.setDirection(myProp.getIntProperty("dirMin"));
		}
		myLog.info("koffer: neue kofferdaten - " + command.toString());
	}

	/**
	 * getMyUrl methode zum erstellen des url"befehles" fuer den koffer
	 */
	private BufferedReader getSite(Command command) throws ConnectException {
		/**
		 * pre: command enhaelt ein null15kommando
		 * 
		 * post: url enth�t die url des koffers mit dem neuen kommando eingebaut
		 */
		// ueberpruefen des command hier!
		checkCommand(command);

		URL url = null;
		BufferedReader br = null;
		try {
			// url zusammenstellen
			StringBuffer caseUrlSB = new StringBuffer();
			caseUrlSB.append(new URL(myProp.getProperty("urlSuitcase"))
					.toString());
			caseUrlSB.append("?submit=Update");
			caseUrlSB.append("&dir=" + command.getDirection());
			caseUrlSB.append("&speed=" + command.getSpeed());
			caseUrlSB
					.append("&stoplim=" + myProp.getProperty("motorStopLimit"));
			myLog.debug("koffer: string fuer url - " + caseUrlSB.toString());

			url = new URL(caseUrlSB.toString());
			br = new BufferedReader(new InputStreamReader(url.openStream()));
		} catch (MalformedURLException e) {
			error(e);

		} catch (IOException e) {
			if (br != null) {
				try {
					br.close();
				} catch (IOException e1) {
				}
			}
			error(e);
		}
		return br;
	}

	/**
	 * setSuitcaseControl setzt die koffersteuerung auf die im parameter command
	 * uebergebenen werte
	 * 
	 * @param command
	 */
	private void doSuitcaseControl(Command command) {
		BufferedReader in = null;
		try {
			in = getSite(command);
			/**
			 * hier werden die Daten geparst und in das sensordata intarray
			 * eingefuegt.
			 */
			SensorData data = parseSite(in);
			connected = true;
			if (!runningRemote) {
				try {
					myKI.calculateNextComand(data, command);
				} catch (UnavoidableObjectException e) {
					myLog.error("koffer: unasuweichbares hindernis: notstopp:"
							+ e.getMessage());
					emergencyStop();
				}
			}

		} catch (NullPointerException e) {
			connected = false;
			error(e);
		} catch (ConnectException e) {
			connected = false;
			error(e);
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
					error(e);
				}
			}
		}
	}

	/**
	 * parseSite methode zum parsen der seite - braucht einen bufferedreader,
	 * der die htmlseite des koffers enth�lt. daraus werden die aktuellen
	 * sensordaten extrahiert und zurueckgegeben.
	 * 
	 * @param br
	 * @return
	 */
	private SensorData parseSite(BufferedReader br) throws NullPointerException {
		/**
		 * pre: br enth�lt einen BufferedReader mit der htmlseite des koffers.
		 * 
		 * post: data enth�lt die aktuellen werte der sensoren aus dem koffer.
		 */
		SensorData data = new SensorData();
		try {
			String str = null;
			StringBuffer sb = new StringBuffer();

			while ((str = br.readLine()) != null) {
				sb.append(str);
			}

			String sensorSite = sb.toString();
			myLog.debug("parser: seite - " + sensorSite);

			/**
			 * matcher die die resultate enthalten.
			 * 
			 * die pattern werden am anfang compiliert, damit dies zur alufzeit
			 * keine performance mehr kostet.
			 */
			// werte der ir sensoren
			Matcher matcher0 = irPattern.matcher(sensorSite);
			if (matcher0.find()) {
				data.setIr1(Integer.parseInt(matcher0.group(1)));
				data.setIr2(Integer.parseInt(matcher0.group(2)));
				data.setIr3(Integer.parseInt(matcher0.group(3)));
				data.setIr4(Integer.parseInt(matcher0.group(4)));
			} else {
				myLog.error("parser: keine matches fuer ir sensoren gefudnen");
			}
			// werte der io sensoren
			Matcher matcher1 = usPattern.matcher(sensorSite);
			if (matcher1.find()) {
				data.setIo1(Integer.parseInt(matcher1.group(1)));
				data.setIo2(Integer.parseInt(matcher1.group(2)));
				data.setIo3(Integer.parseInt(matcher1.group(3)));
				data.setIo4(Integer.parseInt(matcher1.group(4)));
			} else {
				myLog.error("parser: keine matches fuer io sensoren gefudnen");
			}
			// werte der digital sensoren
			Matcher matcher2 = ioPattern.matcher(sensorSite);
			if (matcher2.find()) {
			}

			// logging sensordata
			myLog.info("koffer: daten geparst: " + data.toString());

		} catch (IOException e) {
			error(e);
		}
		return data;
	}

	/**
	 * error methode zum loggen von fehlern.
	 * 
	 * @param e
	 */
	private void error(Exception e) {
		myLog.error("koffer: " + e.toString());
	}

	/**
	 * runRemote methode zum umschalten zwischen einfachem fernsteuer modus und
	 * ki modus. bei parameter true wird der fernsteuermodus aktiviert.
	 * 
	 * @param setIt
	 *            boolscher wert der sagt ob ki ein oder aus ist
	 */
	public void setRemoteMode(boolean setIt) {
		if (setIt) {
			timeout = ((long) myProp.getIntProperty("updateTimeSuitcase"));
			runningRemote = true;
		} else {
			timeout = ((long) myProp.getIntProperty("updateTimeSuitcaseKI"));
			if (myKI == null) {
				myKI = new AICore();
			}
			runningRemote = false;
		}
		myLog.info("koffer: remotemode gewechselt nach: " + runningRemote);
	}

	/**
	 * getCommand methode zum auslesen des aktuellen kommandos.
	 * 
	 * @return aktuelles kommando
	 */
	public Command getCommand() {
		return currentCommand;
	}

	/**
	 * start methode zum starten des thread.
	 */
	public void start() {
		if (!running) {
			running = true;
			myLog.debug("koffer: starte thread.");
			super.start();
		}
	}

	/**
	 * stirb methode zum toeten des thread
	 */
	public void stirb() {
		running = false;
	}

	/**
	 * restart methode - siehe start und stirb methode
	 */
	public void restart() {
		stirb();
		start();
	}

	/**
	 * reset methode zum neu initialisieren der ki
	 */
	public void reset() {
		// setzt aktuelles kommando auf stop und geradeaus und loescht die
		// daten der ki.
		speedStop();
		directionCenter();
	}

	public boolean getRemoteMode() {
		return runningRemote;
	}

	public boolean getConnected() {
		return connected;
	}
}
